UnityAppのViewの上にUIViewとかを置く
概要
Unity advent calendar 22日目です。
http://qiita.com/advent-calendar/2012/unity
iOS用に出力したUnityのパッケージを、Xcodeで作ったプロジェクトから呼ぶ(超手前味噌)
http://sassembla.github.com/Public/2012:12:04%201-29-54/2012:12:04%201-29-54.html
の発展系で、任意のUIViewをaddし、イベントをハンドルする、っつーことをしてみました。
つまり、
プラグインを全く書かずに、Obj-CでUnity上にいろいろ置いたりできるよね、っつー話です。
今回の内容は
・UnityのViewの上に安全にUIViewを置いて、いろいろ動作するか確認
動画
1.Unityのプロジェクトつくって、UnityConnector DLしてきて、プロジェクトにUnityApp組み込んで、シミュレータで動かす直前まで
2.それをSimulatorでビルドするまで
見た目
まず通常のiOSアプリが起動、この時の起動スプラッシュはUnityのものなことに注目
すぐにボタンが一つセットされた画面が表示。
ボタンを押すと、Unityが起動(2度目のスプラッシュ)
一定時間後に
Unityの上に、NativeコードからUIViewを追加
View上のボタンを押すと、ダイアログ表示
このへんのコードは、下記にupしてあります。
コード
githubにupしてあります。 Unityのアプリ部分は含んでいないので、
自分でUnity用のプロジェクトを生成して、iOSUnityApp フォルダに放り込んでね!
https://github.com/sassembla/UnityConnector
解説
アプリケーション内で、どんなことをしているのか
ステップに分けて紹介。
1.タイトルを表示
2.ボタンが押されたらUnityを起動
3.Unityが起動したらUnity上にUIを置く
4.Unity上UIのボタンを押したらアラート
1.タイトルを表示
アプリケーション起動時、ボタンのあるUIを表示
ここはまあ、ただ単に表示してるだけ。
AppDelegate.h
SampleTitleViewController * sampleVCont;
AppDelegate.m
sampleVCont = [[SampleTitleViewController alloc]initSampleTitleViewControllerWithMasterName:CONNECTOR_MASTER];
[self.window addSubview:sampleVCont.view];
ボタンが押されたときのコードは、ただ単にDelegateにそのイベントをぶん投げるもの。
NSNotificationをラップした自作のメッセージングライブラリを使っている。
さらに、AppDelegate.mでは、
m_application = application;
m_launchOptions = [[NSDictionary alloc]init];
みたいな感じで、UIApplicationのインスタンスと
起動時オプションの辞書(こちらは適当に空)
の2つを保持している。
UIApplicationのインスタンスも、起動時オプションも、別に必ずここでとらないといけない訳じゃない。
説明しやすい感じに、起動時に保持するようにしてみただけ。
値は、今後のUnityAppの起動やコントロールに使用する。
2.ボタンが押されたらUnityを起動
自作ライブラリで、ボタンが押された際のイベントが、下記receiverメソッドに飛んでくる。
ボタンが押されると、
SAMPLE_TITLEVIEWCONT_EXEC_UNITY_ON_TAPPED
という名前のイベントがかっ飛んでくる。
コードに①、②とかの番号振っておいたので、それ見つつ解説する。
AppDelegate.m
- (void)receiver:(NSNotification * )notif {
NSString * exec = [messenger getExecFromNotification:notif];
NSDictionary * dict = [messenger getTagValueDictionaryFromNotification:notif];
if ([exec isEqualToString:SAMPLE_TITLEVIEWCONT_EXEC_UNITY_ON_TAPPED]) {
① /*
ignite Unity
*/
[messenger call:KS_UNITYBOOTER withExec:KS_UNITYBOOTER_EXEC_INITIALIZE,
[messenger tag:@"application" val:m_application],
[messenger tag:@"launchOptions" val:m_launchOptions],
nil];
② /*
generate pinView(back button inside.)
*/
SamplePinViewControler * samplePinViewCont = [[SamplePinViewControler alloc]init];
/*
set pinView to Unity-window after some seconds.(this is for experimental.)
*/
[messenger callMyself:CONNECTOR_MASTER_EXEC_SET_VIEW_TO_UNITYWINDOW,
[messenger tag:@"view" val:samplePinViewCont.view],
[messenger withDelay:5.0],
nil];
}
①Unity起動
Unityの起動クラスをラップしているKSUnityBooterのインスタンス(こちらも自作のライブラリ使用)に、
メッセージを送る。
値としてm_applicationとm_launchOptionsを送付。
通信する先は、KSUnityBooter.mm。
ライブラリによって、receiverメソッドへと、イベントが届く。
KSUnityBooter.mmは、UnityAppそれ自体のmain.mmの内容を模している。
必要なimportがあり、intがある。
AppControllerクラスのインスタンスとして、 unityAppという名前のインスタンスを保持するようにしている。
KSUnityBooter.mm
#import "AppController.h"
#import "RegisterClasses.h"
#import "RegisterMonoModules.h"
static const int constsection = 0;
bool UnityParseCommandLine(int argc, char *argv[]);
AppController * unityApp;
- (void) receiver:(NSNotification * )notif {
NSString * exec = [messenger getExecFromNotification:notif];
NSDictionary * dict = [messenger getTagValueDictionaryFromNotification:notif];
//unity-app wrapper with UIApplicationDelegate
/*
o - (void)applicationDidFinishLaunching:(UIApplication *)application;
(中略)
o - (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window NS_AVAILABLE_IOS(6_0);
*/
if ([exec isEqualToString:KS_UNITYBOOTER_EXEC_INITIALIZE]) {
NSAssert([dict valueForKey:@"application"],@"application required");
NSAssert([dict valueForKey:@"launchOptions"], @"launchOptions required");
UIApplication * application = [dict valueForKey:@"application"];
NSDictionary * launchOptions = [dict valueForKey:@"launchOptions"];
RegisterMonoModules();
unityApp = [[AppController alloc]init];
[unityApp application:application didFinishLaunchingWithOptions:launchOptions];
}
ここで、UnityAppの中に入ってる、AppControllerクラスのインスタンスunityAppを初期化。
UnityAppそれ自体が起動したのと同じ挙動にするために、
AppDelegateから送付してきたm_applicationとm_launchOptionsをパラメータに使う。
これで、Unityの起動が開始。
アプリの画面にUnityのスプラッシュが表示されるようになる。
ちなみに、非プロ版時のみ表示されるスプラッシュだけど、用意されたファイルを使わないようにすると
きちんとエラーで落ちる。
悪いことをしてはいけない。
で、UinityApp自体は点火できたので、次はUnity上にiOSのUIを置く。
②Unity上にUIを置く
ここでは、
アラートをセットしてあるボタンが置いてあるUIViewのSamplePinViewControlerをインスタンス化。
そのviewを、5.0秒後にそのインスタンスを自分自身(AppDelegate.mのインスタンス)へと送る。
3.Unityが起動したらUnity上にUIを置く
UnityAppの起動完了 = Unityのスプラッシュが消える、なのだけれど、UnityAppに手を加えないと
それが察知できない。
(書いてて気づいたけどもしかしたらNSNotificationでAccelの開始とかをハンドルすればわかるかもしれない。)
なので、だいたいで5秒たったら、起動してるものとする。
そのタイミングで、下記コードが呼ばれる。
AppDelegate.m
- (void)receiver:(NSNotification * )notif {
(中略)
if ([exec isEqualToString:CONNECTOR_MASTER_EXEC_SET_VIEW_TO_UNITYWINDOW]) {
NSLog(@"m_application %@", [m_application windows]);
NSAssert([dict valueForKey:@"view"], @"view required");
//get the view what want to append to Unity.
UIView * testView = [dict valueForKey:@"view"];
//get window array of this app
NSArray * windowArray = [m_application windows];
// for (UIWindow * window in [m_application windows]) {
// NSLog(@"[window subviews] %@", [window subviews]);
// }
//append view onto Unity-window
if (1 < [windowArray count]) {
[[windowArray objectAtIndex:1] addSubview:testView];
}
}
UnityのViewが作られたあと、Unityが稼働しているAppには、UIWindowが2つある。
旧来の、App起動時からあるUIWindowは実際の画面から引きはがされている(でもnilとかではない、、)
ここでは、2つめのUIWindowを適当に検出して、そこに先ほどのviewをaddSubviewしている。
このUnity用のWIndowは、Unityのインスタンス生成直後から存在はしてるんだけど、
いきなりaddSubviewすると、Unityの起動完了時にremoveされる。
4.Unity上UIのボタンを押したらアラート
UnityのUIWindow上に乗っけているSamplePinViewControlerは、こんな感じ。
SamplePinViewControler.m
- (IBAction)tapped:(id)sender {
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"active" message:@"can react UI-something" delegate:self cancelButtonTitle:@"DONE" otherButtonTitles:nil];
[alertView show];
}
xibで単純に組んでいて、ボタンが押されたら(touchUpInside) アラートを表示するだけ。
以上!